﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using DotNetX;

namespace RPGXEngine
{
    public static class CommandFactory
    {
        #region Public Static Methods

        public static ICommand Create(string command, ScriptManager sm)
        {
            command = command.Trim();

            ICommand cmd = null;

            StringReader reader = new StringReader(command);

            string s = String.Empty;

            // check to see if we are going to be reading in a string, if so, don't call reader.ReadWord as this will fail...
            if (CommandFactory.IsArray(command, sm))
            {
                // do the array check here because the array may contain a string, and thus, break reader.ReadWord
                cmd = CommandFactory.ParseArray(command, sm);
            }
            else if (CommandFactory.IsString(command, sm))
            {
                cmd = CommandFactory.ParseString(command, sm);
            }
            else
            {
                s = reader.ReadWord();

                s = s.Trim();

                if (String.IsNullOrEmpty(command))
                {
                    cmd = new Command_Empty();
                }
                else if (CommandFactory.IsComment(command, sm))
                {
                    cmd = new Command_Empty();
                }
                else if (ConvertExtensions.IsInt32(s))
                {
                    cmd = CommandFactory.ParseScalar(s, sm);
                }
                else if (CommandFactory.IsKeyword(s, sm))
                {
                    cmd = CommandFactory.ParseKeyword(s, reader, sm);
                }
                else if (CommandFactory.IsFunction(s, sm))
                {
                    cmd = CommandFactory.ParseFunction(s, reader, sm);
                }
                else if (CommandFactory.IsBoolean(s, sm))
                {
                    cmd = CommandFactory.ParseBoolean(s, reader, sm);
                }
                else
                {
                    // must be a variable
                    cmd = CommandFactory.ParseVariable(s, reader, sm);
                }

                if (cmd.IsIf)
                {
                }

                // have an OnLoad, OnRender, OnUnLoad etc.. methods in the script instead of a 'main' method
                // have these called at respective points in Game1.cs
            }

            reader.Close();

            return cmd;
        }

        #endregion

        #region Private Static Methods

        private static ICommand ParseKeyword(string s, StringReader reader, ScriptManager sm)
        {
            ICommand cmd = null;

            string command = reader.ReadUntilCharacter(';').Trim();

            DataType d = DataType.Var;
            if (Enum.TryParse<DataType>(s, true, out d))
            {
                switch (d)
                {
                    case DataType.Void:
                        // compiler error as you would never do this...
                        break;
                    case DataType.Var:
                        {
                            cmd = new Command_CreateDataType_Var(command);
                            break;
                        }
                    case DataType.Array:
                        {
                            cmd = new Command_CreateDataType_Array(command);
                            break;
                        }
                    default:
                        // compiler error
                        break;
                }
            }
            else if(s == "return")
            {
                cmd = new Command_Return();
                cmd.ChildrenCommands.Add(CommandFactory.Create(command, sm));
            }
            else if (s == "if" || s == "elseif" || s == "else")
            {
                cmd = new Command_If();

                ((Command_If)cmd).IsElse = s == "elseif" || s == "else";

                // this only allows for one boolean expression per if statement
                // but you can do things like this
                // x = y || z;
                // a = b || c;
                // if(x || a)

                if (!String.IsNullOrEmpty(command))
                {
                    if (command.StartsWith("("))
                        command = command.Remove(0, 1);

                    if (command.EndsWith(")"))
                        command = command.Remove(command.Length - 1, 1);

                    cmd.ChildrenCommands.Add(CommandFactory.Create(command, sm));
                }
            }

            return cmd;
        }

        private static bool IsComment(string s, ScriptManager sm)
        {
            s = s.Trim().ToLower();

            return s.StartsWith("//");
        }

        private static bool IsKeyword(string s, ScriptManager sm)
        {
            bool isKeyword = false;

            s = s.Trim().ToLower();

            // variable decloration
            DataType d = DataType.Var;
            if (Enum.TryParse<DataType>(s, true, out d))
            {
                isKeyword = true;
            }
            else if(s == "return")
            {
                isKeyword = true;
            }
            else if (s == "if")
            {
                isKeyword = true;
            }
            else if (s == "elseif")
            {
                isKeyword = true;
            }
            else if (s == "else")
            {
                isKeyword = true;
            }

            return isKeyword;
        }

        private static ICommand ParseVariable(string s, StringReader reader, ScriptManager sm)
        {
            ICommand rcmd = null;

            string line = reader.ReadToEnd().Trim();
            
            StringReader r = new StringReader(line);

            if (line.Contains('=') || line.Contains('<') || line.Contains('>'))
            {
                IAssignmentCommand assignmentCmd = null;

                Command_Variable cmd = new Command_Variable();

                cmd.Name = s;

                // read the array ordinal before the assignment command
                // TODO read in any methods etc... with the . command
                if (line.StartsWith("["))
                {
                    string array = r.ReadUntilCharacter(' ');
                    cmd = CommandFactory.ReadArrayOrdinal(s, array, sm);
                }

                string assignment = r.ReadUntilCharacter(' ');

                assignment = assignment.ToLower().Trim();

                if (assignment == "=")
                {
                    assignmentCmd = new AssignmentCommand_Equals();
                }
                else if (assignment == "+=")
                {
                    assignmentCmd = new AssignmentCommand_AddEquals();
                }
                else if (assignment == "-=")
                {
                    assignmentCmd = new AssignmentCommand_MinusEquals();
                }
                else if (assignment == "*=")
                {
                    assignmentCmd = new AssignmentCommand_MultiplyEquals();
                }
                else if (assignment == "/=")
                {
                    assignmentCmd = new AssignmentCommand_DivideEquals();
                }
                else if (assignment == "==")
                {
                    assignmentCmd = new AssignmentCommand_IsEqual();
                }
                else if (assignment == ">")
                {
                    assignmentCmd = new AssignmentCommand_IsGreaterThan();
                }
                else if (assignment == ">=")
                {
                    assignmentCmd = new AssignmentCommand_IsGreaterThanOrEqual();
                }
                else if (assignment == "<")
                {
                    assignmentCmd = new AssignmentCommand_IsLessThan();
                }
                else if (assignment == "<=")
                {
                    assignmentCmd = new AssignmentCommand_IsLessThanOrEqual();
                }

                string rightHandSide = r.ReadUntilCharacter(';');

                assignmentCmd.Left = cmd;
                assignmentCmd.Right = CommandFactory.Create(rightHandSide, sm);

                rcmd = assignmentCmd;
            }
            else if(line.StartsWith("[") && line.EndsWith("]"))
            {
                rcmd = CommandFactory.ReadArrayOrdinal(s, line, sm);
            }
            else
            {
                Command_Variable cmd = new Command_Variable();

                cmd.Name = s;

                rcmd = cmd;
            }

            return rcmd;
        }

        private static Command_Variable ReadArrayOrdinal(string name, string line, ScriptManager sm)
        {
            string ordinal = line.Replace("[", String.Empty).Replace("]", String.Empty);

            ICommand ordinalCmd = CommandFactory.Create(ordinal, sm);

            Command_Variable cmd = new Command_Variable();

            cmd.Name = name;
            cmd.ArrayOrdinal = ordinalCmd;

            return cmd;
        }

        private static ICommand ParseString(string s, ScriptManager sm)
        {
            string myString = String.Empty;

            s = s.Trim();

            if (s.StartsWith("\""))
                s = s.Substring(1);

            if (s.EndsWith("\""))
                s = s.Substring(0, s.Length - 1);

            Command_String cmd = new Command_String();

            cmd.Value = s;

            return cmd;
        }

        private static ICommand ParseScalar(string s, ScriptManager sm)
        {
            Command_Scalar cmd = new Command_Scalar();

            cmd.Value = Convert.ToInt32(s);

            return cmd;
        }

        private static ICommand ParseArray(string s, ScriptManager sm)
        {
            Command_Array cmd = new Command_Array();

            s = s.Trim();

            if (s.StartsWith("["))
                s = s.Substring(1);

            if (s.EndsWith("]"))
                s = s.Substring(0, s.Length - 1);

            string [] values = s.Split(new char [] { ',' });

            foreach (var v in values)
            {
                cmd.ChildrenCommands.Add(CommandFactory.Create(v, sm));
            }

            return cmd;
        }

        private static bool IsString(string token, ScriptManager sm)
        {
            return token.Trim().StartsWith("\"") && token.Trim().EndsWith("\"");
        }

        private static bool IsArray(string token, ScriptManager sm)
        {
            return token.Trim().StartsWith("[") && token.Trim().EndsWith("]");
        }

        private static bool IsBoolean(string token, ScriptManager sm)
        {
            return token.Trim() == "true" || token.Trim() == "false";
        }

        private static bool IsFunction(string token, ScriptManager sm)
        {
            return sm.DataTree.FunctionNameIndex.ContainsKey(token);
        }

        private static ICommand ParseFunction(string s, StringReader reader, ScriptManager sm)
        {
            Command_Function cmd = new Command_Function();

            cmd.FunctionAddress = sm.DataTree.FunctionNameAddressIndex[s];

            reader.Read(); // the (

            string args = reader.ReadUntilCharacter(';');

            if (args.Length > 0)
            {
                args = args.Remove(args.Length - 1, 1);

                string[] arguments = args.Split(','); // you cannot nest function calls - just keeping it simple :) -- make it more complex in order to have callbacks with arguments...

                foreach (var a in arguments)
                {
                    ICommand childCmd = CommandFactory.Create(a, sm);
                    if (childCmd == null)
                        continue;

                    cmd.ChildrenCommands.Add(childCmd);
                }
            }

            return cmd;
        }

        private static ICommand ParseBoolean(string s, StringReader reader, ScriptManager sm)
        {
            Command_Boolean cmd = new Command_Boolean();

            cmd.Value = Convert.ToBoolean(s);

            return cmd;
        }

        #endregion
    }
}
